Go Channel

Do not communicate by sharing memory; instead, share memory by communicating.

基本语法

不同类型

1
2
3
4
5
ch := make(<-chan int) // 只读
ch := make(chan<- int) // 只写
ch := make(chan)
ch := make(chan, 0) // 可读写, 不带buffer
ch := make(chan, 100)

发送

1
ch <- v

接收

1
2
3
data := <- ch // 阻塞式
<- ch // 忽略返回数据, 阻塞
data, ok := <- ch // 非阻塞式

关闭

1
close(ch)

buffered vs non-buffered

non-buffered

1
2
3
ch1 := make(chan int)
ch<-1
// 向ch放1后要等别的routine用<-ch接收了这个参数,ch<-1才会继续,否则一直阻塞

buffered

1
2
3
ch2 := make(chan int, 1)
ch<-1
// 因为buffer为1,只有当放第二个值且第一个值没有被读取时阻塞

应用

简单同步

1
2
3
4
5
6
7
8
9
10
11
func main() {
ch := make(chan int)
go F(ch) // 丢进去
fmt.Printf("%d", <-chs) // 接回来
}

func F(ch chan<- int) {
// do sth
time.Sleep(time.Second)
ch <- 1
}

select

语法类似于switch,但功能与select/poll/epoll类似

任意case
1
2
3
4
5
6
7
8
9
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
select {
case <-ch1:
fmt.Pringf("ch1")
case <-ch2:
fmt.Printf("ch2")
}
// 一直等待到任意一个case的语句完成
简单超时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
to := make(chan bool, 1)
ch := make(chan int, 1)
go func() {
time.Sleep(time.Second)
to <- true
}
go func() {
// do sth with long time
ch <- 1
}
select {
case <-ch:
fmt.Printf("done")
case <-to:
fmt.Printf("timed out")
}
// 1秒内执行完则done,超过1s则超时
buffer池
1
2
3
4
5
6
7
8
9
10
ch := make(chan int, 1)
ch <- 1
select {
case ch<-2:
default:
fmt.Printf("chan full")
}
// ch写入1未被读取则ch满
// 当ch要写入2时发现已满,则case阻塞住
// 没有case匹配则执行default,打印chan full

应用场景:

  • 有一个服务,当请求进来的时候我们会生成一个job扔进channel,由其他协程从channel中获取job去执行。
  • 但当channel满时,将该job抛弃并回复【服务繁忙,请稍后再试】,或是将该job放入任务队列。

timer channel: Timer/Ticker

timer

时间到了就通道返回。

1
2
3
4
5
6
timer := time.NewTimer(time.Second*2)
defer timer.Stop() // 注意保险
go func() {
<-timer.C // 等待一秒后channel返回
}()
<-timer.Stop() // 立即停止

ticker

与timer类似,但会重复进行。

1
2
3
4
5
6
7
ticker := time.NewTicker(time.Millisecond * 500) // 每半秒一次计时
defer ticker.Stop() // 注意保险
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()